#include "CSettings.h"
#include "CRegionSceneNode.h"

#include <iostream>
#include <fstream>

#include <sys/stat.h>

CSettings::CSettings(std::string file, CRegionSceneNode *region, std::string defaultfile)
	: Buffer(0), Region(region)
{
	BufferSize = getFileSize(file);
	if(!BufferSize && defaultfile.compare("") != 0)
	{
		file = "data/materials/default.txt";
		BufferSize = getFileSize(file);
	}
	if(BufferSize > 0)
	{
		Buffer = new char[BufferSize + 1];
		if(!readFile(file, Buffer, BufferSize))
		{
			delete [] Buffer;
			Buffer = 0;
			BufferSize = 0;
		}
	}
}

CSettings::~CSettings()
{
	if(Buffer)
	{
		delete [] Buffer;
		Buffer = 0;
		BufferSize = 0;
	}
}

SSettings CSettings::readSettings() const
{
	SSettings s;

	array<std::string> data;
	readSetting("RESOLUTION", "", &data);
	if(data.size() > 0)
		s.Resolution.Width = atoi(data[0].c_str());
	if(data.size() > 1)
		s.Resolution.Height = atoi(data[1].c_str());

	s.Fullscreen = readSetting("FULLSCREEN", 0) == 1;

	return s;
}

std::string CSettings::readSetting(std::string name, std::string defaultvalue, array<std::string> *outdata) const
{
	char *buf = Buffer;
	int len = BufferSize;

	array<std::string> data;

	if(findData(buf, name.c_str(), data))
	{
		if(outdata)
			for(u32 i = 0; i < data.size(); i++)
				outdata->push_back(data[i]);
		if(data.size() > 0)
			return data[0];
	}
	return defaultvalue;
}

int CSettings::readSetting(std::string name, int defaultvalue, array<std::string> *outdata) const
{
	std::string ret = readSetting(name, stringc(defaultvalue).c_str(), outdata);
	return atoi(ret.c_str());
}

f32 CSettings::readSetting(std::string name, f32 defaultvalue, array<std::string> *outdata) const
{
	std::string ret = readSetting(name, stringc(defaultvalue).c_str(), outdata);
	return (f32)atof(ret.c_str());
}

int CSettings::getFileSize(std::string file)
{
	std::ifstream in((file).c_str());
	if(!in.is_open()) return 0;

	std::streamoff begin = in.tellg();
	in.seekg(0, std::ios::end);
	u32 len = (u32)(in.tellg() - begin);
	in.seekg(0, std::ios::beg);
	in.close();
	return len;
}

bool CSettings::nextElem(char **buf, std::string &elem, array<std::string> &outdata)
{
	char *pos = *buf;
	while(pos[0] != '[')
		if(pos[0] == 0)
			return false;
		else
			pos++;

	pos++;
	char *pos2 = pos;
	while(pos2[0] != ':')
		if(pos[0] == 0)
			return false;
		else
			pos2++;

	elem = std::string(pos, pos2);

//	printf("%s\n", elem.c_str());
//	getchar();

	pos2++;
	pos = pos2;

	while(pos2[0] != ']')
		if(pos[0] == 0)
			return false;
		else
			pos2++;

	int datalen = pos2-pos;

	char *temp = new char[datalen+1];
	memcpy(temp, pos, datalen);
	temp[datalen] = 0;
		
	char *tok = strtok(temp, ":");
	while(tok != 0)
	{
		outdata.push_back(std::string(tok));
		tok = strtok(0, ":");
	}

	delete [] temp;
	*buf = pos;
	return true;
}

bool CSettings::parseData(char *buf, std::string &elem, array<std::string> &outdata)
{
	char *pos = strstr(buf, "[");
	if(pos)
	{
		pos++;
		char *pos2 = pos;
		while(pos2[0] != ':')
			pos2++;

		elem = std::string(pos, pos2);

		pos2++;
		pos = pos2;

		while(pos2[0] != ']')
			pos2++;

		int datalen = pos2-pos;

		char *temp = new char[datalen+1];
		memcpy(temp, pos, datalen);
		temp[datalen] = 0;
		
		char *tok = strtok(temp, ":");
		while(tok != 0)
		{
			outdata.push_back(std::string(tok));
			tok = strtok(0, ":");
		}

		delete [] temp;
		return true;
	}
	return false;
}

vector3df CSettings::readVector(array<std::string> &data)
{
	vector3df ret;
	if(data.size() > 0)
		ret.X = (f32)atof(data[0].c_str());
	if(data.size() > 1)
		ret.Y = (f32)atof(data[1].c_str());
	if(data.size() > 2)
		ret.Z = (f32)atof(data[2].c_str());
	return ret;
}

void CSettings::readResources(ISceneManager *mgr, list<SResource*> &resources) const
{
	char *buf = Buffer;
	int len = BufferSize;

	if(!len)
		return;

	array<std::string> data;

	SResource *res = 0;

/*	char *tok = strtok(buf, "\r\n");
	while(tok != 0)
	{
		std::string elem;
		data.clear();
		parseData(tok, elem, data);*/
	std::string elem;
	while(nextElem(&buf, elem, data))
	{
		if(stricmp(elem.c_str(), "NAME") == 0)
		{
			if(res)
				resources.push_back(res);
			std::string name;
			if(data.size() > 0)
				name = data[0];
			res = new SResource(name, 0, 0);
		}
		else if(stricmp(elem.c_str(), "MESH") == 0)
		{
			if(data.size() > 0)
				res->Mesh = mgr->getMesh((std::string("data/meshes/") + data[0]).c_str());
		}
		else if(stricmp(elem.c_str(), "POSITION") == 0)
		{
			if(res)
				res->Position = readVector(data);
		}
		else if(stricmp(elem.c_str(), "ROTATION") == 0)
		{
			if(res)
				res->Rotation = readVector(data);
		}
		else if(stricmp(elem.c_str(), "SCALE") == 0)
		{
			if(res)
				res->Scale = readVector(data);
		}
		else if(stricmp(elem.c_str(), "TILECLASS") == 0)
		{
			int tc = -1;
			if(data.size() > 1)
			{
				res->TileTypeMin = getTileClassFromString(data[0]);
				res->TileTypeMax = getTileClassFromString(data[1]);
			}
			else if(data.size() > 0)
			{
				tc = getTileClassFromString(data[0]);
				res->TileTypeMin = tc;
				res->TileTypeMax = tc;
			}
		}
		data.clear();
	//	tok = strtok(0, "\r\n");
	}

	if(res)
		resources.push_back(res);
}

SResource* CSettings::readResource(std::string name, ISceneManager *mgr) const
{
	char *buf = Buffer;
	int len = BufferSize;

	if(!len)
		return 0;

	array<std::string> data;

	IMesh *mesh = 0;

	// Mesh
	if(findData(buf, "MESH", data))
	{
		if(data.size() > 0)
			mesh = mgr->getMesh((std::string("data/meshes/") + data[0]).c_str());
	}

	if(mesh)
	{
		vector3df pos, rot, scale(1,1,1);

		data.clear();
		if(findData(buf, "POSITION", data))
		{
			if(data.size() > 0)
				pos.X = (f32)atof(data[0].c_str());
			if(data.size() > 1)
				pos.Y = (f32)atof(data[1].c_str());
			if(data.size() > 2)
				pos.Z = (f32)atof(data[2].c_str());
		}

		data.clear();
		if(findData(buf, "ROTATION", data))
		{
			if(data.size() > 0)
				rot.X = (f32)atof(data[0].c_str());
			if(data.size() > 1)
				rot.Y = (f32)atof(data[1].c_str());
			if(data.size() > 2)
				rot.Z = (f32)atof(data[2].c_str());
		}

		data.clear();
		if(findData(buf, "SCALE", data))
		{
			if(data.size() > 0)
				scale.X = (f32)atof(data[0].c_str());
			if(data.size() > 1)
				scale.Y = (f32)atof(data[1].c_str());
			if(data.size() > 2)
				scale.Z = (f32)atof(data[2].c_str());
		}

		data.clear();
		if(findData(buf, "NAME", data))
			if(data.size() > 0)
				name = data[0];

		int tcmin = -1, tcmax = -1, tc = -1;

		data.clear();
		if(findData(buf, "TILECLASS", data))
		{
			if(data.size() > 1)
			{
				tcmin = getTileClassFromString(data[0]);
				tcmax = getTileClassFromString(data[1]);
			}
			else if(data.size() > 0)
			{
				tc = getTileClassFromString(data[0]);
			}
		}

		if(tc != -1)
			return new SResource(name, tc, mesh, 0, pos, rot, scale);
		else if(tcmin != -1 && tcmax != -1)
			return new SResource(name, tcmin, tcmax, mesh, 0, pos, rot, scale);
		return new SResource(name, mesh, 0, pos, rot, scale);
	}
	return 0;
}

SMaterial CSettings::readMaterial(std::string name, IVideoDriver *video) const
{
	SMaterial m;

	char *buf = Buffer;
	int len = BufferSize;

	if(!len)
		return m;

	array<std::string> data;

	// Material type
	if(findData(buf, "MATERIAL_TYPE", data))
	{
		std::string a = data.size() > 0 ? data[0] : "";
		for(u32 i = 0; i < 24; i++)
		{
			if(stricmp(a.c_str(), video::sBuiltInMaterialTypeNames[i]) == 0)
			{
				m.MaterialType = (E_MATERIAL_TYPE)i;
				break;
			}
		}
		SShader *shader = Region->getShaderMaterial(a);
		if(shader)
			m.MaterialType = (E_MATERIAL_TYPE)shader->MaterialType;
	}

	// Texture
	data.clear();
	if(findData(buf, "TEXTURE", data))
	{
		u32 c = data.size();
		for(u32 i = 0; (i+1) < c; i += 2)
		{
			m.setTexture(atoi(data[i].c_str()), video->getTexture(("data/textures/" + data[i+1]).c_str()));
		}
	}

	// Ambient color
	data.clear();
	if(findData(buf, "COLOR_AMBIENT", data))
	{
		m.AmbientColor = SColor(data.size() > 0 ? atoi(data[0].c_str()) : 255,
								data.size() > 1 ? atoi(data[1].c_str()) : 255,
								data.size() > 2 ? atoi(data[2].c_str()) : 255,
								data.size() > 3 ? atoi(data[3].c_str()) : 255);
	//	printf("%d %d %d %d\n", m.AmbientColor.getAlpha(), m.AmbientColor.getRed(), m.AmbientColor.getGreen(), m.AmbientColor.getBlue());
	}

	// Diffuse color
	data.clear();
	if(findData(buf, "COLOR_DIFFUSE", data))
	{
		m.DiffuseColor = SColor(data.size() > 0 ? atoi(data[0].c_str()) : 255,
								data.size() > 1 ? atoi(data[1].c_str()) : 255,
								data.size() > 2 ? atoi(data[2].c_str()) : 255,
								data.size() > 3 ? atoi(data[3].c_str()) : 255);
	}

	// Emissive color
	data.clear();
	if(findData(buf, "COLOR_EMISSIVE", data))
	{
		m.EmissiveColor = SColor(data.size() > 0 ? atoi(data[0].c_str()) : 0,
								data.size() > 1 ? atoi(data[1].c_str()) : 0,
								data.size() > 2 ? atoi(data[2].c_str()) : 0,
								data.size() > 3 ? atoi(data[3].c_str()) : 0);
	}

	// Specular color
	data.clear();
	if(findData(buf, "COLOR_SPECULAR", data))
	{
		m.SpecularColor = SColor(data.size() > 0 ? atoi(data[0].c_str()) : 255,
								data.size() > 1 ? atoi(data[1].c_str()) : 255,
								data.size() > 2 ? atoi(data[2].c_str()) : 255,
								data.size() > 3 ? atoi(data[3].c_str()) : 255);
	}

	// Shininess
	data.clear();
	if(findData(buf, "SHININESS", data))
	{
		m.Shininess = data.size() > 0 ? (f32)atof(data[0].c_str()) : .0f;
	}

	// Thickness
	data.clear();
	if(findData(buf, "THICKNESS", data))
	{
		m.Thickness = data.size() > 0 ? (f32)atof(data[0].c_str()) : .0f;
	}

	// Zbuffer
	data.clear();
	if(findData(buf, "ZBUFFER", data))
	{
		m.ZBuffer = data.size() > 0 ? atoi(data[0].c_str()) : ECFN_LESSEQUAL;
	}

	// Antialiasing
	data.clear();
	if(findData(buf, "ANTIALIASING", data))
	{
		u32 c = data.size();
		if(c > 0)
		{
			m.AntiAliasing = 0;
			for(u32 i = 0; i < c; i++)
				m.AntiAliasing |= atoi(data[i].c_str());
		}
	}

	// Color mask
	data.clear();
	if(findData(buf, "COLOR_MASK", data))
	{
		u32 c = data.size();
		if(c > 0)
		{
			m.ColorMask = 0;
			for(u32 i = 0; i < c; i++)
				m.ColorMask |= atoi(data[i].c_str());
		}
	}

	// Color material
	data.clear();
	if(findData(buf, "COLOR_MATERIAL", data))
	{
		m.ColorMaterial = (E_COLOR_MATERIAL)data.size() > 0 ? atoi(data[0].c_str()) : ECM_DIFFUSE;
	}

	// Wireframe
	data.clear();
	if(findData(buf, "WIREFRAME", data))
	{
		m.Wireframe = data.size() > 0 ? atoi(data[0].c_str()) > 0 : false;
	}

	// Point cloud
	data.clear();
	if(findData(buf, "POINTCLOUD", data))
	{
		m.PointCloud = data.size() > 0 ? atoi(data[0].c_str()) > 0 : false;
	}

	// Gouraud shading
	data.clear();
	if(findData(buf, "GOURAUDSHADING", data))
	{
		m.GouraudShading = data.size() > 0 ? atoi(data[0].c_str()) > 0 : false;
	}

	// Lighting
	data.clear();
	if(findData(buf, "LIGHTING", data))
	{
		m.Lighting = data.size() > 0 ? atoi(data[0].c_str()) > 0 : false;
	}

	// Bilinear filter
	data.clear();
	if(findData(buf, "BILINEAR_FILTER", data))
	{
		m.setFlag(EMF_BILINEAR_FILTER, data.size() > 0 ? atoi(data[0].c_str()) > 0 : true);
	}

	// Trilinear filter
	data.clear();
	if(findData(buf, "TRILINEAR_FILTER", data))
	{
		m.setFlag(EMF_TRILINEAR_FILTER, data.size() > 0 ? atoi(data[0].c_str()) > 0 : false);
	}

	// Anisotropic filter
	data.clear();
	if(findData(buf, "ANISOTRIPIC_FILTER", data))
	{
		m.setFlag(EMF_ANISOTROPIC_FILTER, data.size() > 0 ? atoi(data[0].c_str()) > 0 : false);
	}
	return m;
}

bool CSettings::findData(char *buf, const char *elem, array<std::string> &outdata) const
{
	char *pos = strstr(buf, (std::string("[") + elem + ":").c_str());
	if(pos)
	{
		pos += strlen(elem) + 2;
		char *pos2 = pos;
		while(pos2[0] != ']')
			pos2++;

		int datalen = pos2-pos;

		char *temp = new char[datalen+1];
		memcpy(temp, pos, datalen);
		temp[datalen] = 0;
		
		char *tok = strtok(temp, ":");
		while(tok != 0)
		{
			outdata.push_back(std::string(tok));
			tok = strtok(0, ":");
		}

		delete [] temp;
		return true;
	}
	return false;
}

bool CSettings::readFile(std::string file, char *buf, int &buflen) const
{
	std::ifstream in(file.c_str());
	if(!in.is_open()) return false;

	in.read(buf, buflen);
	return true;
}

bool CSettings::fileExists(std::string name)
{
	struct _stat fi;
	return _stat(name.c_str(), &fi) == 0;
}

void CSettings::readDfMaterials(std::string file, std::string type, std::map<std::string, DFHack::t_matgloss> &outmaterials)
{
	//printf("%s\n", file.c_str());
	std::ifstream in(file.c_str());
	std::streamoff begin = in.tellg();
	in.seekg(0, std::ios::end);
	std::streamoff len = in.tellg();
	len -= begin;
	in.seekg(begin);

	std::string t = "[" + type + ":";

	int typelen = type.length();

	if(len > 0)
	{
		char *buf = new char[(u32)len];
		in.read(buf, len);

		char *pos = buf;
		while((pos = strstr(pos, t.c_str())) != 0 && (pos-buf) < len)
		{
			pos += typelen + 2;
			char *pos2 = pos + 1;
			while((pos2-buf) < len && pos2[0] != ']'){pos2++;}
			pos2[0] = 0;
			char *id = pos;
			pos = pos2+1;
			pos = strstr(pos, "[DISPLAY_COLOR:");
			if(pos != 0)
			{
				pos += 15;
				DFHack::t_matgloss mg;
				pos[1] = 0; pos[3] = 0; pos[5] = 0;
				mg.fore = atoi(pos);
				mg.back = atoi(pos+2);
				mg.bright = atoi(pos+4);

				outmaterials[std::string(id)] = mg;
				//matgloss_inorganic[std::string(id)] = mg;
				pos += 6;
			}
			else
			{
				pos = pos2+1;
			}
		}
		delete [] buf;
	}
}

std::string CSettings::getStringFromTileClass(int tileclass)
{
	switch((DFHack::TileClass)tileclass)
	{
	case DFHack::EMPTY:				return "EMPTY";
        
	case DFHack::WALL:				return "WALL";
	case DFHack::PILLAR:			return "PILLAR";
	case DFHack::FORTIFICATION:		return "FORTIFICATION";
        
	case DFHack::STAIR_UP:			return "STAIR_IP";
	case DFHack::STAIR_DOWN:		return "STAIR_DOWN";
	case DFHack::STAIR_UPDOWN:		return "STAIR_UPDOWN";
        
	case DFHack::RAMP:				return "RAMP";
	case DFHack::RAMP_TOP:			return "RAMP_TOP";
        
	case DFHack::FLOOR:				return "FLOOR";
	case DFHack::TREE_DEAD:			return "TREE_DEAD";
	case DFHack::TREE_OK:			return "TREE_OK";
	case DFHack::SAPLING_DEAD:		return "SAPLING_DEAD";
	case DFHack::SAPLING_OK:		return "SAPLING_OK";
	case DFHack::SHRUB_DEAD:		return "SHRUB_DEAD";
	case DFHack::SHRUB_OK:			return "SHRUB_OK";
	case DFHack::BOULDER:			return "BOULDER";
	case DFHack::PEBBLES:			return "PEBBLES";
	}
	return "";
}

int CSettings::getTileClassFromString(std::string tileclass)
{
	stringc tc(tileclass.c_str());
	tc.make_upper();

	for(u32 i = 0; i <= DFHack::PEBBLES; i++)
	{
		std::string a = getStringFromTileClass(i);
		if(tc == a.c_str())
			return i;
	}

	return -1;
}

std::string CSettings::getNameFromBuildingType(u32 buildingtype)
{
	switch(buildingtype)
	{
	case 1:		return "lever";
	case 3:		return "cage trap";

	case 11:	return "trade depot";

	case 31:	return "kennels";

	case 50:	return "statue";
	case 53:	return "throne";
	case 54:	return "table";
	case 55:	return "bed";

	case 76:	return "bridge";
	case 78:	return "door";
	case 79:	return "armor stand";

	case 80:	return "weapon rack";
	case 81:	return "cabinet";
	case 82:	return "coffer";
	case 83:	return "stockpile";
	case 85:	return "stockpile";
	case 86:	return "stockpile";
	}
	return "";
}